package vn.wallet.app.task;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class BackgroundTask implements Runnable {

	final Object lock = new Object();

	private final static Logger logger = LoggerFactory.getLogger(BackgroundTask.class);

	/**
	 * The task name. It will be automatically set to a defaultname based on the
	 * concrete class name but it can be overriden by any subclass constructor
	 * or <code>computeSteps()</code> method
	 */
	private String name;

	/**
	 * The current step of the execution. It is automatically incremented each
	 * time a subclass calls the <code>stepDone()</code> method.
	 */
	private volatile int currentStep;

	/**
	 * The number of logical steps this task has to perform in its runTask
	 * method. This value is initialized by a call to <code>computeSteps</code>
	 * method
	 */
	private int steps = 1;

	/**
	 * The time this task was started
	 */
	private Date startTime;

	/**
	 * The time this task was stopped, whatever normally or abnormally.
	 */
	private Date endTime;

	/**
	 * The duration of the task, ie the difference between endDate and startDate
	 */
	private long duration;

	/**
	 * Subclasses must implement this method to tell the exact number of steps
	 * that are necessary to perform this task. This value should be the exact
	 * number of times the <code>stepDone()</code> method will be called by the
	 * <code>runTask()</code> method
	 * 
	 * @return
	 * @throws Exception
	 */
	protected abstract int computeSteps() throws Exception;

	/**
	 * Runs the task. This method may return any value. The returned value can
	 * later be examined from the back-office. If the return value is of type
	 * java.io.File, the file will be automatically proposed for downloading.
	 * 
	 * @return
	 * @throws Exception
	 */
	protected abstract Object runTask() throws Exception;

	/**
	 * Subclasses must call this method after each logical step to tell the back
	 * office progress bar the current state of this task. Ideally, this method
	 * must be called exactly <code>n</code> times, <code>n</code> being the
	 * value returned by the <code>computeSteps()</code> method. This is
	 * equivalent to stepDone(1);
	 */
	public final void stepDone() {
		stepDone(1);
	}

	/**
	 * Subclasses may call this method to tell the progress bar that
	 * <code>n</code> steps were successfully accomplished.
	 * 
	 * @param n
	 */
	public final synchronized void stepDone(int n) {
		currentStep += n;
	}

	/**
	 * gets the current progress.
	 * 
	 * @return an int between 0 and 100.
	 */
	public final int getProgress() {
		if (steps == 0) {
			return 100;
		}
		return Math.min((currentStep * 100) / steps, 100);
	}

	/**
	 * Gets the current step, ie the number of times the <code>stepDone()</code>
	 * method was invoked.
	 * 
	 * @return
	 */
	public int getCurrentStep() {
		return currentStep;
	}

	/**
	 * Gets the total number of necessary steps for this task. This value is the
	 * one returned by the <code>computeSteps()</code> method.
	 * 
	 * @return
	 */
	public int getTotalSteps() {
		return steps;
	}

	/**
	 * Sets this task's name
	 * 
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets this task's name
	 * 
	 * @return
	 */
	public String getName() {
		return name;
	}

	/**
	 * Return a string representation of this task.
	 */
	@Override
	public String toString() {
		return getName() + ':' + getProgress();
	}

	/**
	 * Runs this task. Implementation details happen here, but the real job
	 * takes place in the <code>runTask()</code> method.
	 */
	@Override
	public final void run() {
		startTime = new Date();
		try {
			synchronized (this) {
				steps = computeSteps();
				currentStep = 0;
			}
			runTask();
		} catch (Exception e) {

		} finally {
			endTime = new Date();
			duration = endTime.getTime() - startTime.getTime();
			reportDuration();
		}
	}

	/**
	 * Return the duration of this task.
	 * 
	 * @return
	 */
	public long getDuration() {
		return duration;
	}

	/**
	 * Print duration of this task, add it in log messages collection.
	 * 
	 * @return
	 */
	private void reportDuration() {
		logger.info("{} reportDuration.", getName());
		logger.info("startTime {} ", getStartTime());
		logger.info("endTime {} ", getEndTime());
		logger.info("Time of task execution {} ms", getDuration());
	}

	/**
	 * Gets this task's end date.
	 * 
	 * @return
	 */
	public Date getEndTime() {
		return endTime;
	}

	public Date getStartTime() {
		return startTime;
	}
}
